<?php
/**
 * Hoffman Framework
 *
 * LICENSE
 *
 * This source file is subject to the GNU LGPL 2.1 license that is bundled
 * with this package in the file license.txt.
 * It is also available through the world-wide-web at this URL:
 * http://creativecommons.org/licenses/LGPL/2.1
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to annop@thaicyberpoint.com so we can send you a copy immediately.
 *
 * @package	   Hoffman
 * @author     Ford AntiTrust
 * @copyright  Copyright (c) 2008 Ford AntiTrust (http://www.thaicyberpoint.com/ford/blog)
 * @since	   Version Rv 0.2
 * @license    http://creativecommons.org/licenses/LGPL/2.1
 *             GNU LGPL 2.1
 * @filesource
 */

if (! defined('INDEX'))
    die('No direct script access allowed.');

/**
 * @see Zend_Registry
 */
require_once 'Zend/Registry.php';

/**
 * @see Zend_Db
 */
require_once 'Zend/Db.php';

/**
 * @see Zend_Db_Statement
 */
require_once 'Zend/Db/Statement.php';

/**
 * @see Zend_Db_Expr
 */
require_once 'Zend/Db/Expr.php';

/**
 * @see Zend_Db_Table_Abstract
 */
require_once 'Zend/Db/Table/Abstract.php';

/**
 * @see Hmf_Model
 */
require_once 'Hmf/Model.php';

/**
 * @see Hmf_Pagination
 */
require_once 'Hmf/Pagination.php';

/**
 * @see Hmf_Validate
 */
require_once 'Hmf/Validate.php';


/**
 * Db
 *
 * @package		Hoffman
 * @subpackage	Hmf_Model
 * @category	Db
 * @author		Ford AntiTrust
 * @version     SVN: $Id: Db.php 167 2010-02-18 10:32:16Z annop.s@gmail.com $
 * @link		http://www.thaicyberpoint.com/ford/blog/hmf
 */

class Hmf_Model_Db extends Zend_Db_Table_Abstract implements Hmf_Model {

    private $db;

    private $pagination;

    private $info;

    private $hasLoadConstructor = false;

    private $profile;

    private $connected = false;

    private static $dbConnect = array();

    private static $cache;

    private static $prepareStmt = array();

    protected $validate;

    const PREFIX_CACHE_DB = 'qc_';

    function __construct($modelName = null, $profile = 'default') {

        $this->profile = $profile;

        if (! $this->hasLoadConstructor) {

            $this->_name = is_null($modelName) ? preg_replace('/^model_/', '', get_class($this)) : $modelName;

            $this->validate = new Hmf_Validate();

            $this->hasLoadConstructor = true;

            if(!isset(Hmf_Model_Db::$cache)) {
                $cacheFrontends = array(
                    'lifetime' =>  3600,
                    'automatic_serialization' => true
                );
                $cacheBackends = array(
                    'cache_dir' => PATH_DIR_TMP_CACHE_DB,
                    'file_name_prefix' => 'hmf_db',
                    'hashed_directory_umask' => '0777',
                    'cache_file_umask' => '644',
                    'hashed_directory_level' => '2'
                );
                Hmf_Model_Db::$cache = Zend_Cache::factory('Core', 'File', $cacheFrontends, $cacheBackends);
            }

        }
    }

    public function __toString() {

        return $this->_name;
    }

    function dbConnect() {
        if (!$this->connected) {
            $this->setDbProfile($this->profile);
        }
    }

    /**
     * โหลด profile ของ database จาก Config เข้ามาให้กับ factory ของ Zend_Db
     *
     * @param String $name
     */
    final function setDbProfile($name = 'default') {

        $app_config = Zend_Registry::get('app_config');

        if(!isset(Hmf_Model_Db::$dbConnect[$name])) {
            Hmf_Model_Db::$dbConnect[$name] = Zend_Db::factory($app_config['database'][$name]['adapter'], $app_config['database'][$name]);
        } else {
            $this->connected = true;
        }

        $this->db = & Hmf_Model_Db::$dbConnect[$name];

        parent::__construct(array(Zend_Db_Table_Abstract::ADAPTER => $this->db, Zend_Db_Table_Abstract::METADATA_CACHE => Hmf_Model_Db::$cache));

        $this->info = parent::info();

        /*
         * Specified for MySQL database
         */
        if(!$this->connected) {
            if (strcasecmp($app_config['database'][$name]['adapter'], 'pdo_mysql') === 0 or strcasecmp($app_config['database'][$name]['adapter'], 'mysqli') === 0) {

                if (! empty($app_config['database'][$name]['charset'])) {
                    self::query('SET CHARACTER SET ' . $app_config['database'][$name]['charset']);
                }
                if (! empty($app_config['database'][$name]['collation'])) {
                    self::query('SET collation_connection = ' . $app_config['database'][$name]['collation']);
                }
            }
        }
    }

    public function used() {

        return $this->db;
    }

    public function cacheSave($data, $key, $specificLifetime = false, array $tags = array()) {

        return Hmf_Model_Db::$cache->save($data, self::PREFIX_CACHE_DB.$this.'_'.$key, array_merge($tags, array('querycache')), $specificLifetime);

    }

    public function cacheLoad($key) {

        return Hmf_Model_Db::$cache->load(self::PREFIX_CACHE_DB.$this.'_'.$key);

    }

    public function removeCache($key) {

        return Hmf_Model_Db::$cache->remove(self::PREFIX_CACHE_DB.$this.'_'.$key);

    }

    public function cleanCache(array $tags = array()) {

        return Hmf_Model_Db::$cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array_merge($tags, array('querycache')));

    }

    final public function _pagination($page = 1, $viewPerPage = 25, $where = null, $order = null) {

        $this->pagination = new Hmf_Pagination($page, $this->count($where), $viewPerPage);

        return $this->fetchAll($where, $order, $this->pagination->getViewPerPage(), $this->pagination->getOffset())->toArray();
    }

    final public function _paginationWithQuery(Zend_Db_Select $sql, $totalsRecords = null, $page = 1, $viewPerPage = 25) {

        $rows = $sql->limitPage($page, $viewPerPage)->query()->fetchAll();

        if( is_null( $totalsRecords )) {
            $sql->reset(Zend_Db_Select::COLUMNS);
            $sql->reset(Zend_Db_Select::LIMIT_COUNT);
            $sql->reset(Zend_Db_Select::LIMIT_OFFSET);
            $sql->columns(array('total' => self::_expr('COUNT(*)')));
            $totalsRecords = $sql->query()->fetchAll();
            $totalsRecords = $totalsRecords[0]['total'];
        }

        $this->pagination = new Hmf_Pagination($page, $totalsRecords, $viewPerPage);

        return  $rows;

    }

    final public function paginationLink($url, $id = null, $numShowPages = null, $strSubfixPage = '/') {

        if ($this->pagination instanceof Hmf_Pagination) {
            return $this->pagination->getLink($url, $id, $numShowPages, $strSubfixPage);
        } else {
            return null;
        }
    }

    final public function paginationLinkWithArray($url, $type = null, $id = null, $numShowPages = null, $strSubfixPage = '/') {

        $this->pagination->processLink($url, $id, $numShowPages, $strSubfixPage);

        return $this->pagination->getLinkWithArray($type);
    }

    final public function getPaginationInstance() {

        return $this->pagination;
    }

    public function prepare($str) {

        $this->dbConnect();
        return self::used()->prepare($str);
    }

    public function createPrepareStmt($key, $stmt) {

        if (! self::hasPrepareStmt($key)) {
            self::$prepareStmt[$key] = $this->prepare($stmt);
        }
    }

    public function hasPrepareStmt($key) {

        return array_key_exists($key, self::$prepareStmt);
    }

    public function executePrepareStmt($key, $params = null) {

        if (self::hasPrepareStmt($key)) {
            // Zend_Db_Statment|PDOStatement
            if (self::$prepareStmt[$key] instanceof Zend_Db_Statement or self::$prepareStmt[$key] instanceof PDOStatement) {
                self::$prepareStmt[$key]->execute($params);
            }
        }
    }

    public function query($str, $bind = array()) {

        $this->dbConnect();
        return self::used()->query($str, $bind);
    }

    public function count($where = null) {

        $this->dbConnect();
        if (is_null($where)) {
            $row = self::query($this->read()->from($this->__toString(), array('total' => self::_expr('COUNT(*)'))))->fetchAll();
        } else {
            $row = self::query($this->read()->from($this->__toString(), array('total' => self::_expr('COUNT(*)')))->where($where))->fetchAll();
        }

        return $row[0]['total'];
    }

    final public function executeValidate(array $data) {

        return $this->validate->validate($data);
    }

    final public function addValidate($rule, $inputName, array $args = array(), $userErrorMessage = null) {

        $this->validate->addRule($inputName, $rule, $args, $userErrorMessage);

        return $this;
    }

    final public function removeValidate($inputName) {

    	$this->validate->removeRule($inputName);

        return $this;
    }

    final public function addValidateAliasInputName($inputName, $alias) {

        $this->validate->addKeywordAlias($inputName, $alias);
    }

    final public function getValidatedResult() {

        return $this->validate->getResults();
    }

    final public function getValidatedRules() {

        return $this->validate->getRules();
    }

    final public function getValidatedResultErrorMsg() {

        $errorout = null;
        $validateInfo = self::getValidatedResult();
        $hasError = false;

        $errorout .= '<ul>'.PHP_EOL;
        foreach ($validateInfo as $keyword => $info) {
            foreach($info as $rule => $ruleInfo) {
                if ($ruleInfo['bool'] === false) {
                    foreach ($ruleInfo['message'] as $msg) {
                        $errorout .= '<li>'.t($this->validate->getKeywordAlias($keyword)) . ': ' . $msg . '</li>'.PHP_EOL;
                    }
                    $hasError = true;
                }
            }
        }
        $errorout .= '</ul>'.PHP_EOL;

        if ($hasError) {
            return $errorout;
        } else {
            return true;
        }
    }

    final public function isValid($keyword, $value) {

        if (!$this->validate->isValid($keyword, $value))
            return false;

        return true;
    }

    public function __call($method, $args) {

        $this->dbConnect();
        if (strcmp($method, 'read') == 0) {

            if (count($args) > 0) {
                // $where, $order, $count, $offset
                return parent::fetchAll($args[0], $args[1], $args[2], $args[3])->toArray();
            } else {

                return self::used()->select();
            }

        } elseif (strcmp($method, 'pagination') == 0 or strcmp($method, 'paginationWithQuery') == 0) {

            if($args[0] instanceof Zend_Db_Select) {

                 // Zend_Db_Select $sql, $totalsRecords = null, $page = 1, $viewPerPage = 25
                return $this->_paginationWithQuery($args[0], $args[1], $args[2], $args[3]);
            } else {

                // $page = 1, $viewPerPage = 25, $where = null, $order = null
                return $this->_pagination($args[0], $args[1], $args[2], $args[3]);
            }
        }

        return false;

    }

    public function insert(array $data) {

        $this->dbConnect();
        if (self::executeValidate($data)) {

            return parent::insert($data);
        } else {

            return false;
        }

    }

    public function update(array $data, $where) {

        $this->dbConnect();
        return parent::update($data, $where);
    }

    public function delete($where) {

        $this->dbConnect();
        return parent::delete($where);
    }

    public function quoteInto($text, $value, $type = null, $count = null) {

        $this->dbConnect();
        return self::used()->quoteInto($text, $value, $type, $count);
    }

    public function quote($value, $type = null) {

        $this->dbConnect();
        return self::used()->quote($value, $type);
    }

    public function _expr($expr) {

        return new Zend_Db_Expr($expr);
    }

    public function _or() {

        $numArgs = func_num_args();
        if ($numArgs > 0) {
            $args = func_get_args();
            return implode(' OR ', $args);
        }
        return ' OR ';
    }

    public function _and() {

        $numArgs = func_num_args();
        if ($numArgs > 0) {
            $args = func_get_args();
            return implode(' AND ', $args);
        }
        return ' AND ';
    }
}
?>